<?php
// +----------------------------------------------------------------------
// | ThinkSNS
// +----------------------------------------------------------------------
// | Copyright (c) 2009 http://www.thinksns.com All rights reserved.
// +----------------------------------------------------------------------
// | Author: Daniel Yang <desheng.young@gmail.com>
// +----------------------------------------------------------------------
//

/**
 +------------------------------------------------------------------------------
 * DomainAuthService 域认证与域帐户搜索服务
 * 实现Windows和Linux系统下与AD服务器的连接与帐户校验
 * 为用户域认证和同事搜索提供支持
 +------------------------------------------------------------------------------
 * @category	addons
 * @package		addons
 * @subpackage  services
 * @author		Daniel Yang <desheng.young@gmail.com>
 * @version		$Id$
 +------------------------------------------------------------------------------
 */

class DomainAuthService extends Service {
	private $host = ""; //AD服务器
    private $port = "";
    private $user = "";
    private $pswd = "";

	private $dao        = array();	//
	private $group		= array();	//AD群组名. 'CorpUsers':华为员工; 'PartnerUsers':合作方
	private $white_list = array();  //白名单
	private $server_os	= '';		//服务器类型

	private $time		= 0;

	public function __construct($data) {
		//服务初始化
		$this->init($data);
	}

	//根据配置初始化
	public function init($data = '') {
		$this->dao = model('Xdata');
		$domain = $this->dao->lget('domain');

		//设置用户组
		if(empty($data)) {
			$this->setGroup($domain['ou']);
		}else {
			$this->setGroup($data);
		}

		//设置白名单
		$temp = array();
		$list = explode("\r\n", $domain['white_list']);
		foreach($list as $v) {
			$v = explode(' ', $v);
			$temp[$v[0]] = $v[1];
		}
		$this->white_list = $temp;
		unset($temp);

		include SITE_PATH.'/addons/libs/3des.php';
		$crypt	=	new Crypt3DES;

		//其他参数设置
		$this->host		 = $domain['host'];
		$this->port		 = $domain['post'];
		$this->user		 = $domain['username'];
		$this->pswd		 = $crypt->decrypt($domain['password']);
		$this->server_os = eregi('WIN', PHP_OS) ? 'WIN' : 'OTHER';
	}

	public function getWhiteList() {
		return $this->white_list;
	}

	//运行服务
	public function run(){

	}

	public function microtime_float() {
		list($usec, $sec) = explode(" ", microtime());
		return ((float)$usec + (float)$sec);
	}

	public function getTime() {
		return $this->time;
	}

	//获取当前群组
	public function getGroup() {
		return $this->group;
	}

	//设置群组限制 ['CorpUsers':华为员工/'PartnerUsers':合作方]
	public function setGroup($group) {
		if( is_string($group) ) {
			$this->group = explode(',',str_replace(' ','',$group));
		}elseif ( is_array($group) ) {
			$this->group = $group;
		}
	}

    //获取服务器类型
    public function getServerOs() {
        return $this->server_os;
    }

	//获取用户域名/用户名信息
	public function getRemoteUser() {
		return $_SERVER["REMOTE_USER"];
	}

	//获取域名信息
	public function getDomain() {
		list($domain, $username) = explode('\\',$_SERVER["REMOTE_USER"]);
		return $domain;
	}

	//获取用户名信息
	public function getUserName() {
		list($domain, $username) = explode('\\',$_SERVER["REMOTE_USER"]);
		return $username;
	}

	//自动登录
	public function autoLogin() {
		$username = $this->getUserName();
		return $this->checkUsername($username);
	}

	//检查用户名是否有效
	public function checkUsername($username) {
		if( !empty($username) ) {
			return $this->_checkDomainAuth($username);
		} else {
			return false;
		}
	}

	//检查用户名/密码是否有效
	public function checkPassword($username, $password) {

//		$u2	=	strtoupper(substr($username,1,2));
//		$u3	=	substr($username,1,3);
//
//		$acl	=	array('HC','KF','GW','WX','WY','HZ');
//
//		//华赛直接屏蔽
//		if(strlen($username)==8 && $u3=='900'){
//			return false;
//
//		//验证2、3位数是否符合HC,KF,GW,WX,WY,HZ,900去CorpUsers,OverseaCorpUsers组去验证
//		}if( in_array($u2,$acl) ){
//			$domain_auth->setGroup('PartnerUsers,CorpUsers,OverseaCorpUsers');
//
//		//其他用户去PartnerUsers,OverseaPartnerUsers,CorpUsers,OverseaCorpUsers组去验证
//		}else{
//			$domain_auth->setGroup('PartnerUsers,OverseaPartnerUsers,CorpUsers,OverseaCorpUsers');
//		}

		//验证白名单配置
		$white_list = $this->white_list;
		if(isset($white_list[$username])){
			include SITE_PATH.'/addons/libs/3des.php';
			$crypt	=	new Crypt3DES;
			return ($white_list[$username]==$crypt->encrypt($password));
		}

		if($username == 'hi' AND $password=='xsw2XSW@') return true;

		if ( (!empty($username)) && (!empty($password)) ) {
			return $this->_checkDomainAuth($username, $password);
		} else {
			//用户名或密码不能为空
			return -2;
		}
	}

	//根据输入的关键字从AD上匹配用户名
	public function getFuzzyMatching($key, $limit = 0) {
		if( empty($key) || !is_numeric($limit) ) {
			return array();
		}

		$result = array();
		$time_start = $this->microtime_float();

		if($this->server_os == 'WIN') {
			$result = $this->_getFuzzyMatchingWindows($key, $limit);
		}else {
			$result = $this->_getFuzzyMatchingOther($key, $limit);
		}

		$this->time = $this->microtime_float() - $time_start;
//		echo 'This search takes ' . $this->time . ' seconds.';

		return $result;
	}

	protected function _getFuzzyMatchingWindows($key, $limit = 0) {
		if( (!is_numeric($key[0])) && (is_numeric($key[3]))) { //e.g. w00135495
			$result = $this->searchForSAM($key, $limit);
		}elseif (is_numeric($key)) { //e.g. 0013549
			$result = $this->searchForSn($key, $limit);
		}else { //e.g. wangbaomin
			$result = $this->searchForCn($key, $limit);
		}

        //提取结果集的前 $limit 条
		if($limit > 0) {
			$result = $this->_limitArray($result, $limit);
		}

        return $result;
	}

	protected function _getFuzzyMatchingOther($key, $limit = 0) {
		$result = array();
		//建立LDAP连接
		$ldap_conn = @ldap_connect($this->host, $this->port)
			or die("Could not connect to AD Host.");
		@ldap_set_option($ldap_conn, LDAP_OPT_PROTOCOL_VERSION, 3)
			or die ("Could not set ldap protocol");
		$ldap_bind = @ldap_bind($ldap_conn, $this->user, $this->pswd)
			or die ("Could not bind");

		//遍历group数组，在每个group中检索
		foreach($this->group as $v) {
			$dn = "OU=" . $v . ",DC=china,DC=huawei,DC=com";
			//"(& (objectcategory=person)(objectClass=user) (|(sn={0}*)(cn={0}*)(sAMAccountName={0}*)))";
			$filter = "(& (objectcategory=person)(objectClass=user) (| (cn=$key*)(sAMAccountName=$key*)(sn=$key*) ) )";
			$attrs = array("cn","sn","givenName","sAMAccountName","description");
			$search = ldap_search($ldap_conn, $dn, $filter, $attrs);
			$temp_result = ldap_get_entries($ldap_conn, $search);
			if( !empty($temp_result) ) {
				//格式化结果集，按照预定格式输出
				$temp_result = $this->_formatArray($temp_result);
				$result = array_merge($result, $temp_result);
			}
		}

		//提取结果集的前 $limit 条
		if($limit > 0) {
			$result = $this->_limitArray($result, $limit);
		}
		@ldap_unbind($ldap_conn);

		return $result;
	}

	//查询cn属性
	public function searchForCn($key, $limit = 0) {
		$result = array();
		if( empty($key) ) {
			return $result;
		}

		foreach($this->group as $k => $v) {
			$strRS = "SELECT cn, sn,sAMAccountName,givenName,description FROM 'LDAP://lggad03-dc.china.huawei.com/";
			if( $v != '' ) {
				$strRS = $strRS . "OU=" . $v . ",";
			}
			$strRS = $strRS . "DC=china,DC=huawei,DC=com' WHERE objectcategory='person' AND objectClass='user' AND cn='" . $key . "*' ORDER BY cn";
			$result = array_merge($result, $this->_searchForFuzzyMatching($strRS));
		}

		return $result;
	}

	//查询sn属性
	public function searchForSn($key, $limit = 0) {
		$result = array();
		if( empty($key) ) {
			return $result;
		}

		foreach($this->group as $k => $v) {
			$strRS = "SELECT cn, sn,sAMAccountName,givenName,description FROM 'LDAP://lggad03-dc.china.huawei.com/";
			if( $v != '' ) {
				$strRS = $strRS . "OU=" . $v . ",";
			}
			$strRS = $strRS . "DC=china,DC=huawei,DC=com' WHERE objectcategory='person' AND objectClass='user' AND sn='" . $key . "*' ORDER BY cn";
			$result = array_merge($result, $this->_searchForFuzzyMatching($strRS));
		}

		return $result;
	}

	//查询SAMAccountName属性
	public function searchForSAM($key, $limit = 0) {
		$result = array();
		if( empty($key) ) {
			return $result;
		}

		foreach($this->group as $k => $v) {
			$strRS = "SELECT cn, sn,sAMAccountName,givenName,description FROM 'LDAP://lggad03-dc.china.huawei.com/";
			if( $v != '' ) {
				$strRS = $strRS . "OU=" . $v . ",";
			}
			$strRS = $strRS . "DC=china,DC=huawei,DC=com' WHERE objectcategory='person' AND objectClass='user' AND SAMAccountName='" . $key . "*' ORDER BY cn";
			$result = array_merge($result, $this->_searchForFuzzyMatching($strRS));
		}

		return $result;
	}

	/**
     +----------------------------------------------------------
     * 连接AD进行域认证
     +----------------------------------------------------------
     * @access public
     +----------------------------------------------------------
     * @param string $username 用户名
     * @param string $password 密码     [可选 | 默认值: 空]
     +----------------------------------------------------------
     * @return int
     +----------------------------------------------------------
     * @see $username 姓的拼音的第一个字母小写+工号ID
     * @see $password 密码为空时，仅验证用户名是否存在于AD服务器
     +----------------------------------------------------------
     */
	protected function _checkDomainAuth($username, $password='') {
		if( empty($username) ) {
			return false;
		}

		if($this->server_os == 'WIN') {
			return $this->_checkDomainAuthWindows($username, $password);
		}else {
			return $this->_checkDomainAuthOther($username, $password);
		}
	}

	protected function _checkDomainAuthWindows($username, $password = '') {
		$dn = "";
		$result_username = false;
		$result_password = true;

		foreach ($this->group as $k => $v) {
			//初始化连接
			$errMsg= "";
			$Conn = New COM("ADODB.Connection");
			$RS = New COM("ADODB.Recordset");

			$Conn->Provider = "ADsDSOObject";
			$strConn = "Active Directory Provider";
			$Conn->Open($strConn);

			//组装查询语句
			$strRS = "Select distinguishedName from 'LDAP://lggad03-dc.china.huawei.com/";
			if( $v != '' ) {
				$strRS = $strRS . "OU=" . $v . ",";
			}
			//'CorpUsers':华为员工/'PartnerUsers':合作方
			$strRS = $strRS . "DC=china,DC=huawei,DC=com' where objectcategory='person' and objectClass='user' and SAMAccountName='" . $username . "'";
			//echo $strRS.'<br />';

			//一次查询,根据SAMAccountName检查distinguishedName
			try {
				$RS->Open($strRS, $Conn, 1, 1);
			}catch (Exception $e) {
				$Conn->Close;
				$errMsg = "Open ldap fail: ".$e->getMessage();
				//echo "<SCRIPT LANGUAGE=\"javascript\"> alert(\"$errMsg\");</SCRIPT> ";
				return false;
			}

			try {
				while (!$RS->EOF) {
					$dn = $RS->Fields['distinguishedName']->Value;
					$RS->MoveNext();
				}
			}catch (Exception $e) {
				$RS->Close;
				$Conn->Close;
				$errMsg = "query distinguishedName fail: ".$e->getMessage();
				//echo "<SCRIPT LANGUAGE=\"javascript\"> alert(\"$errMsg\");</SCRIPT> ";
				return false;
			}

			$RS->Close;
			$Conn->Close;

			if ( $dn == "" ) {
				$errMsg = "user isn't existed.";
				//echo "<SCRIPT LANGUAGE=\"javascript\"> alert(\"$errMsg\");</SCRIPT> ";
				//return false;
				continue;
			}else {
				$result_username = true;
				break;
			}
		} //End foreach

		//echo '查询用户名结束<br />';
		if( $dn == '' ) {
			return false;
		}

		//二次查询,根据绑定用户检查密码
		if( $password != '' ){
			$result_password = false;
			foreach ($this->group as $k => $v) {
				//初始化连接
				$errMsg= "";
				$Conn = New COM("ADODB.Connection");
				$RS = New COM("ADODB.Recordset");

				$Conn->Provider = "ADsDSOObject";
				$strConn = "Active Directory Provider";
				//$Conn->Open($strConn);

				//组装查询语句
				$strRS = "Select distinguishedName from 'LDAP://lggad03-dc.china.huawei.com/";
				if( $v != '' ) {
					$strRS = $strRS . "OU=" . $v . ",";
				}
				//'CorpUsers':华为员工/'PartnerUsers':合作方
				$strRS = $strRS . "DC=china,DC=huawei,DC=com' where objectcategory='person' and objectClass='user' and SAMAccountName='" . $username . "'";

				$Conn->Properties['User ID'] = $dn;
				$Conn->Properties['Password'] = $password;
				try {
					$Conn->Open($strConn);
				}catch (Exception $e) {
					$errMsg = "password is error: ".$e->getMessage();
					//echo "<SCRIPT LANGUAGE=\"javascript\"> alert(\"$errMsg\");</SCRIPT> ";
					return false;
				}

				try {
					$RS->Open($strRS, $Conn, 1, 1);
				}catch (Exception $e) {
					$Conn->Close;
					$errMsg = "check user and password fail: ".$e->getMessage();
					//echo "<SCRIPT LANGUAGE=\"javascript\"> alert(\"$errMsg\");</SCRIPT> ";
					//return false;
					continue;
				}

				$dn2 = "";
				try {
					while (!$RS->EOF) {
						$dn2 = $RS->Fields['distinguishedName']->Value;
						$RS->MoveNext();
					}
				}catch (Exception $e) {
					$RS->Close;
					$Conn->Close;
					$errMsg = "query fail by distinguishedName: ".$e->getMessage();
					//echo "<SCRIPT LANGUAGE=\"javascript\"> alert(\"$errMsg\");</SCRIPT> ";
					//return false;
					continue;
				}

				$RS->Close;
				$Conn->Close;
				//echo "<SCRIPT LANGUAGE=\"javascript\"> alert(\"binding= $dn2\");</SCRIPT> ";

				if ( $dn2 == "" ) {
					$errMsg = "check user and password fail!";
					//echo "<SCRIPT LANGUAGE=\"javascript\"> alert(\"$errMsg\");</SCRIPT> ";
					//return false;
					continue;
				}else {
					$result_password = true;
					break;
				}
			}//end foreach
		} // end if

		return ($result_username && $result_password);
	}

	protected function _checkDomainAuthOther($username, $password='') {
		$distinguishedName = '';
		//建立LDAP连接
		$ldap_conn = @ldap_connect("ldap://china.huawei.com", "389")
		or die("Could not connect to AD Host.");
		@ldap_set_option($ldap_conn, LDAP_OPT_PROTOCOL_VERSION, 3)
		or die ("Could not set ldap protocol");
		$ldap_bind = @ldap_bind($ldap_conn, $this->user, $this->pswd)
		or die ("Could not bind");

		//第一次查询：查询给定用户是否存在
		foreach($this->group as $v) {
			$dn = "OU=" . $v . ",DC=china,DC=huawei,DC=com";
			$filter = "(|(sAMAccountName=" . $username . "))";
			$attrs = array("distinguishedName");
			$search = ldap_search($ldap_conn, $dn, $filter, $attrs);
			$result = ldap_get_entries($ldap_conn, $search);
			if($result['count']==0) {
				continue;
			}else {
				$distinguishedName = $result[0]['dn'];
				break;
			}
		}
		if( empty($distinguishedName) ) {
			return false;
		}

		//第二次查询：检查给定用户名/密码是否匹配
		if(!empty($password)) {
			$ldap_bind = @ldap_bind($ldap_conn, $distinguishedName, $password);
			if( $ldap_bind == false) {
				return false;
			}
		}

		@ldap_unbind($ldap_conn);
		return true;
	}

	//根据查询语句查询结果，返回2维数组
	protected function _searchForFuzzyMatching($strRS, $limit) {
		$result = array();

		if( empty($strRS) ) {
			return $result;
		}

    	if( is_numeric($limit) && ($limit > 0) ) {
			$use_limit = true;
		}

		$Conn = New COM("ADODB.Connection");
		$RS = New COM("ADODB.Recordset");

		$Conn->Provider = "ADsDSOObject";
		$strConn = "Active Directory Provider";
		$Conn->Open($strConn);

		try {
			$RS->Open($strRS, $Conn, 1, 1);
		}catch (Exception $e) {
			$Conn->Close;
			$errMsg = "Open ldap fail: ".$e->getMessage();
			//echo "<SCRIPT LANGUAGE=\"javascript\"> alert(\"$errMsg\");</SCRIPT> ";
			return $result;
		}

		$i = 1;
		try {
			while (!$RS->EOF) {
				//$department = $this->get_Hrbi_info( $RS->Fields['sAMAccountName']->Value );
				$i = $RS->Fields['description']->Value;
				$result = array_merge($result, array(array(
				'cn'			=> $RS->Fields['cn']->Value,
				'sn'			=> $RS->Fields['sn']->Value,
				'short'			=> $RS->Fields['sAMAccountName']->Value,
				'name'			=> $RS->Fields['givenName']->Value,
				'department'	=> auto_charset( (string) $i['Cn'] ,'gb2312', 'UTF-8'),
				)));

				if( ($use_limit != false) && ($i >= $limit) ) {
					break;
				}
				$RS->MoveNext();
				$i++;
			}
		}catch (Exception $e) {
			$RS->Close;
			$Conn->Close;
			$errMsg = "query distinguishedName fail: ".$e->getMessage();
			//echo "<SCRIPT LANGUAGE=\"javascript\"> alert(\"$errMsg\");</SCRIPT> ";
			return $result;
		}

		$RS->Close;
		$Conn->Close;

		return $result;
	}



	protected function _formatArray($array) {
		if( empty($array) ) {
			return $array;
		}
		//格式化数组的输出格式
		$result = array();
		foreach($array as $v) {
			if(!is_array($v)) {
				continue;
			}
			$result[] = array(
			"cn"    	  => $v["cn"][0],
			"sn"    	  => $v["sn"][0],
			"short" 	  => $v["samaccountname"][0],
			"name"		  => $v["givenname"][0],
			"department" => $v['description'][0] ,//临时用IT [ 使用description时返回object(variant) / 使用HRBI填充 ]
			);
		}
		//排序
		usort($result, "_cmp");
		return $result;
	}

	protected function _cmp($a, $b) {
		strcmp($a['cn'], $b['cn']);
	}

	protected function _limitArray($array, $limit) {
		$result = array();
		$i = 1;
		foreach($array as $v) {
			$result[] = $v;
			$i++;
			if($i > $limit) {
				break;
			}
		}
		return $result;
	}

	/* 后台管理相关方法 */

	//启动服务，未编码
	public function _start(){
		return true;
	}

	//停止服务，未编码
	public function _stop(){
		return true;
	}

	//卸载服务，未编码
	public function _install(){
		return true;
	}

	//卸载服务，未编码
	public function _uninstall(){
		return true;
	}
}
?>